package optimizers.localsearch.de;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import common.RunStatus;
import common.Static;

import model.Enzyme;
import model.OligoSystemGeneral;
import fitness.Fitness;
import fitness.FitnessResult;
import fitness.MathFitness;
import optimizers.commons.Parameter;
import optimizers.commons.RunConfig;
import optimizers.commons.evaluator.FitnessEvaluationService;
import optimizers.ganeat.Connection;
import optimizers.ganeat.Constants;
import optimizers.ganeat.Node;
import optimizers.ganeat.math.IndividualMath;
import optimizers.ganeat.oscillator.robustfaststrong.ConstantsRobustFSO;
import optimizers.ganeat.report.LoggerNEAT;
import optimizers.ganeat.square.IndividualSquare;
import optimizers.localsearch.AbstractParamOptimizer;
import optimizers.localsearch.cmaes.CMAES;
import runconfiguration.SquareConfiguration;
import util.MyUtil;

public class DE {

	ArrayList<Double> minRange;
	ArrayList<Double> maxRange;

	protected Random rnd;
	protected int PopSize = 50;
	protected int GenCount;
	RunConfig config;
	String templateSystem;
	FitnessEvaluationService evaluationService;
	ArrayList<int[]> sets;
	private boolean maximize;

	DEIndiv CurGen[];
	DEIndiv NextGen[];

	public DE(String templateSystem, RunConfig config) {
		this.maximize = true;
		this.templateSystem = templateSystem;
		this.rnd = new Random(System.currentTimeMillis());
		this.GenCount = 0;
		evaluationService = new FitnessEvaluationService(9, new MathFitness(),
				null, null);
		IndividualMath indiv = new IndividualMath(
				new HashMap<String, Integer>(), new HashMap<String, String>(),
				-1, 0, config, templateSystem);
		sets = MyUtil.getSets(Static.seqK.length, indiv.getNSimpleSequences());
		this.config = config;
	}

	/*******
	 * Method to convert the flattened parameter array (double[] x) into
	 * OligoSystemGeneral class
	 * 
	 * @param x
	 * @return
	 */

	private OligoSystemGeneral getOligoModel(double[] x) {
		IndividualMath indiv = getIndividual(x);
		return indiv.getOligoModel();
	}

	private IndividualMath getIndividual(double[] x) {
		int count = 0;
		IndividualMath indiv = new IndividualMath(
				new HashMap<String, Integer>(), new HashMap<String, String>(),
				-1, 0, config, templateSystem);
		double[] params = new double[indiv.getNSimpleSequences()];
		System.arraycopy(x, 0, params, 0, params.length);
		int[] pos = MyUtil.doubleToIntSet(params);
		for (int i = 0; i < indiv.nodeGenes.size(); i++) {
			Node node = indiv.nodeGenes.get(i);
			if (node.type == Node.SIMPLE_SEQUENCE
			// && !node.name.equals("b")
			) {
				node.string = Static.seqStr[pos[count]];
				node.parameter = Static.seqK[pos[count]];
				count++;
			}
		}
		for (int i = 0; i < indiv.connectionGenes.size(); i++) {
			Connection conn = indiv.connectionGenes.get(i);
			conn.parameter = x[count];
			count++;
		}
		return indiv;
	}

	/*******
	 * Method to convert the OligoSystemGeneral into flattened double vector of
	 * parameters to be optimized by DE
	 * 
	 * @param x
	 * @return
	 */

	private double[] getParameterArray(IndividualMath indiv) {

		double params[] = new double[indiv.getNSimpleSequences()
				+ indiv.connectionGenes.size()];

		int count = 0;
		for (int i = 0; i < indiv.nodeGenes.size(); i++) {
			Node node = indiv.nodeGenes.get(i);
			if (node.type == Node.SIMPLE_SEQUENCE
			// && !node.name.equals("b")
			) {
				params[count] = MyUtil.strToDouble(node.string);
				count++;
			}
		}
		for (int i = 0; i < indiv.connectionGenes.size(); i++) {
			Connection conn = indiv.connectionGenes.get(i);
			params[count] = conn.parameter;
			count++;
		}
		System.out.print("Params: ");
		for (int i = 0; i < params.length; i++) {
			System.out.print(params[i] + " ");
		}
		System.out.println();
		return (params);
	}

	/**********************
	 * Method to generate the initial DE population and convert them into an
	 * array of OligSystemGeneral models and store the DE inidiviuals parameters
	 * e.g. crossRate / ampFactors
	 */

	public OligoSystemGeneral[] createInitGeneration(IndividualMath indiv) {

		OligoSystemGeneral[] initGen;

		this.minRange = new ArrayList<Double>();
		this.maxRange = new ArrayList<Double>();

		for (int i = 0; i < indiv.getNSimpleSequences(); i++) {
			this.minRange.add(new Double(0));
			this.maxRange.add(new Double(1));
		}

		for (int i = 0; i < indiv.connectionGenes.size(); i++) {
			this.minRange.add(new Double(0.01));
			this.maxRange.add(new Double(60));
		}

		this.CurGen = new DEIndiv[this.PopSize];
		this.NextGen = new DEIndiv[this.PopSize];

		initGen = new OligoSystemGeneral[this.PopSize];

		// first randomly place the template individual in the population
		int index = (int) (this.rnd.nextDouble() * this.PopSize);

		double[] param = getParameterArray(indiv);
		this.CurGen[index] = new DEIndiv(param, this.rnd);
		initGen[index] = getOligoModel(this.CurGen[index].getParameters());
		this.CurGen[index].setOligoSystem(initGen[index]);
		index = (index + 1) % this.PopSize;

		// now create other initial individuals randomly
		for (int i = 0; i < this.PopSize; ++i) {
			this.CurGen[index] = new DEIndiv(param.length, this.minRange,
					this.maxRange, this.rnd);
			initGen[index] = getOligoModel(this.CurGen[index].getParameters());
			this.CurGen[index].setOligoSystem(initGen[index]);
			index = (index + 1) % this.PopSize;
		}

		return initGen;
	}

	// @OVERRIDE the parent method because of DE's one to one generation
	// alternation mechanism
	public OligoSystemGeneral optimize() {
		IndividualMath indiv = new IndividualMath(
				new HashMap<String, Integer>(), new HashMap<String, String>(),
				-1, 0, config, templateSystem);
		OligoSystemGeneral[] curGen = createInitGeneration(indiv);
		OligoSystemGeneral[] nextGen;
		FitnessResult[] fitnessResult = evaluationService
				.evaluateFitness(curGen);
		double[] curFitness = new double[fitnessResult.length];
		double[] nextFitness;

		for (int i = 0; i < fitnessResult.length; i++) {
			if (fitnessResult[i] != null) {
				curFitness[i] = fitnessResult[i].finalResult();
			} else {
				curFitness[i] = 0;
			}
			this.CurGen[i].setFitnessScore(curFitness[i]);
		}

		// debug
		// printCurGen();

		int bestIndivIndex = 0;
		while (!stopEvolution()) {
			nextGen = getNextGeneration(curGen, curFitness);
			fitnessResult = evaluationService.evaluateFitness(nextGen);
			nextFitness = new double[fitnessResult.length];
			for (int i = 0; i < fitnessResult.length; i++) {
				if (fitnessResult[i] != null) {
					nextFitness[i] = fitnessResult[i].finalResult();
				} else {
					nextFitness[i] = 0;
				}
				this.NextGen[i].setFitnessScore(nextFitness[i]);

			}

			for (int i = 0; i < curGen.length; i++) {
				if (nextFitness[i] >= curFitness[i]) {
					curGen[i] = nextGen[i];
					this.CurGen[i] = this.NextGen[i];
					curFitness[i] = nextFitness[i];
				}
			}

			bestIndivIndex = 0;
			double avgFitness = 0;
			for (int i = 0; i < curGen.length; i++) {
				if (curFitness[i] > curFitness[bestIndivIndex]) {
					bestIndivIndex = i;
				}
				avgFitness += curFitness[i];
			}
			avgFitness /= curGen.length;

			// debug
			// printCurGen();

			System.out.println("Gen " + this.GenCount + " Best fitness index: "
					+ bestIndivIndex + " Best fitness score: "
					+ curFitness[bestIndivIndex] + " Avg fitness score: "
					+ avgFitness);
			IndividualMath bestIndiv = getIndividual(this.CurGen[bestIndivIndex]
					.getParameters());
			System.out.println(bestIndiv.toString());
		}
		return curGen[bestIndivIndex];
	}

	public OligoSystemGeneral[] getNextGeneration(OligoSystemGeneral[] curGen,
			double[] results) {
		// TODO Auto-generated method stub
		OligoSystemGeneral[] oligoSystemGen = new OligoSystemGeneral[this.PopSize];

		int a, b, c;

		for (int i = 0; i < this.PopSize; ++i) {
			do {
				a = (int) (this.rnd.nextDouble() * this.PopSize);
			} while (a == i);
			do {
				b = (int) (this.rnd.nextDouble() * this.PopSize);
			} while (b == i || b == a);
			do {
				c = (int) (this.rnd.nextDouble() * this.PopSize);
			} while (c == i || c == a || c == b);

			this.NextGen[i] = this.CurGen[i].breed(this.CurGen[a],
					this.CurGen[b], this.CurGen[c], this.rnd, this.minRange,
					this.maxRange);
		}

		for (int i = 0; i < this.PopSize; ++i) {
			oligoSystemGen[i] = getOligoModel(this.NextGen[i].getParameters());
			this.NextGen[i].setOligoSystem(oligoSystemGen[i]);
		}

		return oligoSystemGen;
	}

	public boolean stopEvolution() {
		// TODO Auto-generated method stub
		++this.GenCount;
		if (this.GenCount > DEConstants.MaxGen) {
			return true;
		} else
			return (false);

	}

	public static void main(String[] args) {

		RunConfig config = new RunConfig();
		config.inhK = new Parameter(RunConfig.INHK_OPTIMIZE_NO_PRESET, 0, 0, 0,
				new double[] {});
		System.out.println("Initializing DE");
		String template = Static.prototypes.get("Gaussian 1");

		DE de = new DE(template, config);
		OligoSystemGeneral optimizedSystem = de.optimize();
	}

}
